package com.george.es.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.george.es.model.dto.FileDto;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xiaoleilu.hutool.util.CollectionUtil;
import com.xiaoleilu.hutool.util.HtmlUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.mail.util.MimeMessageParser;
import org.apache.commons.mail.util.MimeMessageUtils;
import org.apache.poi.hslf.extractor.PowerPointExtractor;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.activation.DataSource;
import javax.annotation.Resource;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 * <p>
 *
 * </p>
 *
 * @author GeorgeChan 2019/6/18 10:52
 * @version 1.0
 * @since jdk1.8
 */
public class ReadFileUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReadFileUtils.class);
    @Resource
    private PropertiesUtils propertiesUtils;

    public static FileDto readPpt(String path) {
        String suffix = path.substring(path.lastIndexOf(".") + 1);
        FileDto fileDto = null;

        switch (suffix) {
            case "pptx":
                fileDto = getFileInfoFormPPT2007(path);
                break;
            case "ppt":
                fileDto = getFileInfoFromPPT2003(path);
                break;
            case "doc":
                fileDto = getFileInfoFromWord2003(path);
                break;
            case "docx":
                fileDto = getFileInfoFromWord2007(path);
                break;
            case "txt":
                fileDto = getFileInfoFromTxt(path);
                break;
            case "zip":
                fileDto = getFileInfoFromZip(path);
                break;
            case "rar":
                fileDto = getFileInfoFromLinuxRar(path);
                break;
            case "html":
                fileDto = getFileInforFromHtml(path);
                break;
            case "xml":
                fileDto = getFileInfoFromXml(path);
                break;
            case "json":
                fileDto = getFileInfoFromJson(path);
                break;
            case "eml":
                fileDto = getFileInfoFromEml(path);
                break;
            default:
                LOGGER.info("不支持的文档类型！");
        }
        return fileDto;
    }

    /**
     * 从邮件中读取邮件内容和附件
     * @param path 文件路径
     * @return 文件信息
     */
    private static FileDto getFileInfoFromEml(String path) {
        File file = new File(path);
        FileDto fileDto = new FileDto();
        if (file.exists()) {
            try {
                MimeMessage mimeMessage = MimeMessageUtils.createMimeMessage(null, file);
                MimeMessageParser parser = new MimeMessageParser(mimeMessage);
                // 发件人
                String from = parser.getFrom();
                // 主题
                String subject = parser.getSubject();
                // 收件人
                List<Address> to = parser.getTo();
                String plainContent;
                if (parser.parse().hasPlainContent()) {
                    plainContent = parser.parse().getPlainContent();
                    plainContent = DelSpecialCharPatternUtils.replaceBlank(plainContent);
                }
                // 获取所有的邮件附件
                List<File> emailFiles = getAllEmailFile(file, parser);
                // 获取附件的文件信息
                List<FileDto> fileDtos = Lists.newArrayList();
                fileDtos = getEmailFileDtoList(emailFiles, fileDtos);
                System.out.println(fileDtos);

            } catch (MessagingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return fileDto;
    }

    /**
     * 获取文件信息
     * @param emailFiles email附件列表
     * @return 文件信息集合
     */
    private static List<FileDto> getEmailFileDtoList(List<File> emailFiles, List<FileDto> fileDtos) {
        for (File file : emailFiles) {
            if (file.exists() && file.isFile()) {
                FileDto fileDto = readPpt(file.getAbsolutePath());
                fileDtos.add(fileDto);
            }else if (file.exists() && file.isDirectory()) {
                File[] files = file.listFiles();
                for (File file1 : files) {
                    readPpt(file1.getAbsolutePath());
                }
            }
        }
        return fileDtos;
    }

    /**
     * 获取所有的文件附件
     * @param file 原解析的邮件文件
     * @param parser 邮件信息对象
     * @return 文件列表
     */
    private static List<File> getAllEmailFile(File file, MimeMessageParser parser) {
        List<File> fileList = Lists.newArrayList();
        // 附件列表
        List<DataSource> attachmentList;
        // 获取所有附件
        try {
            if (parser.parse().hasAttachments()) {
                attachmentList = parser.parse().getAttachmentList();
                if (CollectionUtil.isNotEmpty(attachmentList)) {
                    // 去重
                    Map<String, DataSource> dataSourceMap = Maps.newHashMap();
                    for (DataSource data : attachmentList) {
                        String name = data.getName();
                        if (StringUtils.isNotEmpty(name)) {
                            dataSourceMap.put(name, data);
                        }
                    }
                    for (String fileName : dataSourceMap.keySet()) {
                        DataSource dataSource = dataSourceMap.get(fileName);
                        InputStream inputStream = dataSource.getInputStream();
                        String emailFilePath = file.getParent()+File.separator+RandomStringUtils.randomNumeric(13)+File.separator+fileName;
                        File newFile = FileUtil.writeFromStream(inputStream, new File(emailFilePath));
                        fileList.add(newFile);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fileList;
    }

    /**
     * 解析json文件，读取文件内容
     * @param path 文件路径
     * @return 返回文本信息
     */
    private static FileDto getFileInfoFromJson(String path) {
        File file = new File(path);
        if (file.exists()) {
            String content = FileUtil.readUtf8String(file);
            content = DelSpecialCharPatternUtils.replaceBlank(content);

            FileDto fileDto = new FileDto();
            fileDto.setTitle(file.getName());
            fileDto.setContent(content);
            fileDto.setCreateTime(new Date());
            fileDto.setUpdateTime(new Date());
            return fileDto;
        }
        return null;
    }

    /**
     * 从xml文件中提取文本信息
     * @param path 文件路径
     * @return 文件信息
     */
    private static FileDto getFileInfoFromXml(String path) {
        File file = new File(path);
        if (file.exists()) {
            try {
                //1、创建一个DocumentBuilderFactory的对象
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                //2、创建一个DocumentBuilder的对象
                DocumentBuilder db = dbf.newDocumentBuilder();
                //3、通过DocumentBuilder对象的parser方法加载books.xml文件到当前项目下
                /*注意导入Document对象时，要导入org.w3c.dom.Document包下的*/
                //传入文件名可以是相对路径也可以是绝对路径
                Document document = db.parse(path);
                String content = XmlUtil.toStr(document);
                content = DelSpecialCharPatternUtils.replaceBlank(content);

                FileDto fileDto = new FileDto();
                fileDto.setTitle(file.getName());
                fileDto.setContent(content);
                fileDto.setCreateTime(new Date());
                fileDto.setUpdateTime(new Date());
                return fileDto;
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 从html文件中读取文本信息
     * @param path 文件路径
     * @return 文件读取信息
     */
    private static FileDto getFileInforFromHtml(String path) {
        File file = new File(path);
        if (file.exists()) {
            String htmlStr = FileUtil.readUtf8String(file);
            String content = HtmlUtil.cleanHtmlTag(htmlStr);
            content = DelSpecialCharPatternUtils.replaceBlank(content);

            FileDto fileDto = new FileDto();
            fileDto.setTitle(file.getName());
            fileDto.setContent(content);
            fileDto.setCreateTime(new Date());
            fileDto.setUpdateTime(new Date());
            return fileDto;
        }
        return null;
    }

    /**
     * 从rar压缩文件中获取所有的文件内容
     * <p>
     *     window环境下可执行，
     *     需要win10安装winrar
     * </p>
     * @param path 文件路径
     * @return 文件信息
     */
    private static FileDto getFileInforFromWinRar(String path) {
        List<FileDto> fileDtos = Lists.newArrayList();
        File file = new File(path);
        String winrarPath = "F:\\systemsoft\\winrar\\WinRAR.exe";
        List<File> fileList = Lists.newArrayList();
        List<File> rarAllFiles = getRarAllFiles(file, winrarPath, fileList);
        for (File file1 : rarAllFiles) {
            LOGGER.info("文件名==》 {}，    文件路径==》  {},  绝对路径==》   {}", file1.getName(), file1.getPath(), file1.getAbsolutePath());
            FileDto fileDto = ReadFileUtils.readPpt(file1.getAbsolutePath());
            fileDtos.add(fileDto);
            System.out.println(fileDto);
        }
        return null;
    }

    /**
     * linux环境下，从rar中获取所有的文件内容
     * <p>
     *     linux环境必须安装相 rarlinux 解压工具
     *     参考https://www.jianshu.com/p/8199d21f74fe?utm_source=oschina-app
     * </p>
     * @param path 文件路径
     * @return 文件信息
     */
    private static FileDto getFileInfoFromLinuxRar(String path) {
        LOGGER.info("文件路径===》 {}", path);
        List<FileDto> fileDtos = Lists.newArrayList();
        File file = new File(path);
        LOGGER.info("文件是否存在===》 {}", file.exists());
        // 压缩命令
//        String rarCmd = "rar a ";
        // 解压命令
        String unrarCmd = "rar x ";
        List<File> fileList = Lists.newArrayList();
        List<File> unrarFileList = getUnrarFileForLinux(file, unrarCmd, fileList);
        for (File file1 : unrarFileList) {
            LOGGER.info("文件名==》 {}，    文件路径==》  {},  绝对路径==》   {}", file1.getName(), file1.getPath(), file1.getAbsolutePath());
            FileDto fileDto = ReadFileUtils.readPpt(file1.getAbsolutePath());
            fileDtos.add(fileDto);
            System.out.println(fileDto);
        }
        return null;
    }

    /**
     * 在linux系统中rar文件解压并获取所有解压的文件
     * @param file 目标文件
     * @param unrarCmd 解压命令
     * @param fileList 文件列表
     * @return 返回的文件列表
     */
    private static List<File> getUnrarFileForLinux(File file, String unrarCmd, List<File> fileList) {
        LOGGER.info("");
        if (file.exists() && file.isFile()) {
            if (StringUtils.containsIgnoreCase(file.getName(), "rar")) {
                File unrarDir = getUnrarDirFromLinux(file, unrarCmd);
                getUnrarFileForLinux(unrarDir, unrarCmd, fileList);
            }else {
                fileList.add(file);
            }
        } else if (file.exists() && file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                if (f != null) {
                    getUnrarFileForLinux(f, unrarCmd, fileList);
                }
            }
        }
        return fileList;
    }

    /**
     * 在linux环境中解压文件，并返回解压后的文件
     * @param file 要解压的文件
     * @param unrarCmd 解压命令
     * @return 解压后的文件
     */
    private static File getUnrarDirFromLinux(File file, String unrarCmd) {
        String targetPath = file.getParentFile().getAbsolutePath()+ File.separator + UUID.randomUUID() + RandomStringUtils.randomNumeric(13) + File.separator;
        LOGGER.info("解压目录===》 {}", targetPath);
        unrarCmd += file.getAbsolutePath() + StringPool.SPACE + targetPath;
        LOGGER.info("解压命令===》 {}", unrarCmd);

        boolean bool = false;
        File unrarFile = null;
        try {
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec(unrarCmd);
            if (proc.waitFor() != 0) {
                if (proc.exitValue() == 0) {
                    bool = false;
                }
            } else {
                bool = true;
            }
            if (bool) {
                LOGGER.info("解压成功===》 {}", targetPath);
                unrarFile = new File(targetPath);
                return unrarFile;
            } else {
                LOGGER.info("解压失败====》 {}", targetPath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return unrarFile;
    }

    /**
     * 从zip文件中获取文件内容
     * @param path 文件路径
     * @return 文件内容
     */
    private static FileDto getFileInfoFromZip(String path) {
        List<FileDto> fileDtos = Lists.newArrayList();
        List<File> fileList = Lists.newArrayList();
        try {
            File fileDir = ZipUtil.unzip(path, Charset.forName("GBK"));
            // 递归获取所有文件
            fileList = getZipAllFiles(fileDir, fileList);
            for (File file : fileList) {
                LOGGER.info("文件名==》 {}，    文件路径==》  {},  绝对路径==》   {}", file.getName(), file.getPath(), file.getAbsolutePath());
                FileDto fileDto = ReadFileUtils.readPpt(file.getAbsolutePath());
                fileDtos.add(fileDto);
                System.out.println(fileDto);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取zip里面的所有的文件
     * @param file 文件
     * @return 文件集合
     */
    private static List<File> getZipAllFiles(File file, List<File> fileList) {
        if (file.exists() && file.isFile()) {
            if (StringUtils.containsIgnoreCase(file.getName(), "zip")) {
                List<File> newFileList = Lists.newArrayList();
                File fileDir = ZipUtil.unzip(file, Charset.forName("GBK"));
                getZipAllFiles(fileDir, newFileList);
            } else {
                fileList.add(file);
            }
        } else if (file.exists() && file.isDirectory()){
            File[] files = file.listFiles();
            for (File f : files) {
                if (f != null) {
                    getZipAllFiles(f, fileList);
                }
            }
        }
        return fileList;
    }

    /**
     * 通过winrar获取所有解压的文件
     * @param file 要处理的文件
     * @param winrarPath winrar安装路径
     * @param fileList 文件集合
     * @return 处理完之后返回的文件列表
     */
    public static List<File> getRarAllFiles(File file, String winrarPath, List<File> fileList) {
        String targetPath = file.getParentFile().getAbsolutePath()+ File.separator + RandomStringUtils.randomNumeric(13);
        File dicFile = new File(targetPath);
        if (file.exists() && file.isFile()) {
            if (StringUtils.containsIgnoreCase(file.getName(), "rar")) {
                if (!dicFile.exists()) {
                    dicFile.mkdir();
                }
                String cmd = winrarPath + " x -r -p- -o+ " + file.getAbsolutePath() + StringPool.SPACE + targetPath;
                boolean bool = false;
                try {
                    Process proc = Runtime.getRuntime().exec(cmd);
                    if (proc.waitFor() != 0) {
                        if (proc.exitValue() == 0) {
                            bool = false;
                        }
                    } else {
                        bool = true;
                    }
                    if (bool) {
                        File unrarFile = new File(targetPath);
                        getRarAllFiles(unrarFile, winrarPath, fileList);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                fileList.add(file);
            }
        } else if (file.exists() && file.isDirectory()){
            File[] files = file.listFiles();
            for (File f : files) {
                if (f != null) {
                    getRarAllFiles(f, winrarPath, fileList);
                }
            }
        }
        return fileList;
    }

    /**
     * 读取Txt文件内容
     * @param path 文件路径
     * @return 文件信息
     */
    private static FileDto getFileInfoFromTxt(String path) {
        File file;
        FileInputStream fis;
        ByteArrayOutputStream bos;
        String title = StringPool.EMPTY;
        String content = StringPool.EMPTY;
        try {
            file = new File(path);
            fis = new FileInputStream(file);
            bos = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int len;
            while((len = fis.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            byte[] fileByte = bos.toByteArray();
            // 文件编码
            String charset = get_charset(fileByte);
            // 获取文件内容
            content = new String(fileByte, charset);
            // 去除特殊符号
            content = DelSpecialCharPatternUtils.replaceBlank(content);
            title = file.getName();
        } catch (Exception e) {
            e.printStackTrace();
        }
        FileDto fileDto = new FileDto();
        fileDto.setTitle(title);
        fileDto.setContent(content);
        fileDto.setCreateTime(new Date());
        fileDto.setUpdateTime(new Date());
        return fileDto;
    }

    /**
     * 读取word2007文件内容
     * @param path 文件路径
     * @return 文件内容
     */
    private static FileDto getFileInfoFromWord2007(String path) {
        FileInputStream in2007;
        String title = StringPool.EMPTY;
        String content = StringPool.EMPTY;
        try {
            File file = new File(path);
            in2007 = new FileInputStream(file);
            XWPFDocument doc2007=new XWPFDocument(in2007);
//            List<XWPFPictureData> allPictures = doc2007.getAllPictures();
            // 获取图片
//            int pictureType = allPictures.get(0).getPictureType();
//            byte[] data = allPictures.get(0).getData();
            // 文档解析
            XWPFWordExtractor word2007=new XWPFWordExtractor(doc2007);
            // 获取文档内容
            content = word2007.getText();
            // 去除特殊符号
            content = DelSpecialCharPatternUtils.replaceBlank(content);
            title = file.getName();
        } catch (Exception e) {
            e.printStackTrace();
        }
        FileDto fileDto = new FileDto();
        fileDto.setTitle(title);
        fileDto.setContent(content);
        fileDto.setCreateTime(new Date());
        fileDto.setUpdateTime(new Date());
        return fileDto;
    }

    /**
     * 读取word2003  doc文本内容
     * @param path 文件路径
     * @return 文件内容
     */
    private static FileDto getFileInfoFromWord2003(String path) {
        FileInputStream in2003;
        String content = StringPool.EMPTY;
        String title = StringPool.EMPTY;
        // 文档解析
        WordExtractor word2003;
        try {
            File file = new File(path);
            in2003 = new FileInputStream(file);
            word2003 = new WordExtractor(in2003);
            // 获取文本
            content = word2003.getText();
            // 去除特殊符号
            content = DelSpecialCharPatternUtils.replaceBlank(content);
            title = file.getName();
        } catch (IOException e) {
            e.printStackTrace();
        }
        FileDto fileDto = new FileDto();
        fileDto.setTitle(title);
        fileDto.setContent(content);
        fileDto.setCreateTime(new Date());
        fileDto.setUpdateTime(new Date());
        return fileDto;
    }

    /**
     * 读取ppt2007版本的文件内容
     * @param path 文件路径
     * @return 读取信息
     */
    private static FileDto getFileInfoFormPPT2007(String path) {
        FileInputStream fis;
        XMLSlideShow slide;
        String title = StringPool.EMPTY;
        String content=StringPool.EMPTY;
        File file;
        try {
            file = new File(path);
            fis = new FileInputStream(file);
            slide = new XMLSlideShow(fis);
            XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(slide);
            content = extractor.getText();
            title = file.getName();
            // 去除特殊符号
            content = DelSpecialCharPatternUtils.replaceBlank(content);
            extractor.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        FileDto fileDto = new FileDto();
        fileDto.setTitle(title);
        fileDto.setContent(content);
        fileDto.setCreateTime(new Date());
        fileDto.setUpdateTime(new Date());
        return fileDto;
    }

    /**
     * 读取ppt2003文件
     * @param path 文件路径
     * @return 返回信息
     */
    private static FileDto getFileInfoFromPPT2003(String path) {
        FileInputStream fis;
        PowerPointExtractor ex;
        String title = StringPool.EMPTY;
        String content=StringPool.EMPTY;
        File file;
        try {
            file = new File(path);
            // word 2003： 图片不会被读取
            fis = new FileInputStream(file);
            ex = new PowerPointExtractor(fis);
            content = ex.getText();
            // 去除特殊符号
            content = DelSpecialCharPatternUtils.replaceBlank(content);
            title = file.getName();
            ex.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        FileDto fileDto = new FileDto();
        fileDto.setTitle(title);
        fileDto.setContent(content);
        fileDto.setCreateTime(new Date());
        fileDto.setUpdateTime(new Date());
        return fileDto;
    }


    /**
     * 获取txt文件编码格式
     * @param file 文件字节
     * @return 编码字符串
     * @throws IOException 异常
     */
    private static String get_charset(byte[] file) throws IOException {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        InputStream bis = null;
        try {
            boolean checked = false;
            bis = new ByteArrayInputStream(file);
            bis.mark(0);
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1)
                return charset;
            if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE";
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB
                    && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8";
                checked = true;
            }
            bis.reset();
            if (!checked) {
                while ((read = bis.read()) != -1) {
                    if (read >= 0xF0)
                        break;
                    if (0x80 <= read && read <= 0xBF) // 单独出现BF以下的，也算是GBK
                        break;
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) // 双字节 (0xC0 - 0xDF)
                            // (0x80 - 0xBF),也可能在GB编码内
                            continue;
                        else
                            break;
                    } else if (0xE0 <= read && read <= 0xEF) {// 也有可能出错，但是几率较小
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else
                                break;
                        } else
                            break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                bis.close();
            }
        }
        return charset;
    }

    public static void main(String[] args) {
//        String path = "F:\\other\\test\\PPT_test1.pptx";
//        String path = "F:\\other\\test\\PPT_test2.ppt";
//        String path = "F:\\other\\test\\测试文档doc.doc";
//        String path = "F:\\other\\test\\测试文档docx.docx";
//        String path = "F:\\other\\test\\测试txt.txt";
//        String path = "F:\\other\\test\\test.zip";
//        String path = "F:\\other\\test\\test1.zip";
//        String path = "F:\\other\\test\\test1.rar";
//        String path = "F:\\other\\test\\java读取html文件博客园.html";
//        String path = "F:\\other\\test\\ArticleMapper.xml";
//        String path = "F:\\other\\test\\testjson.json";
        String path = "F:\\other\\test\\测试读取eml文件.eml";
//        String tmpPath = propertiesUtils.getTmpPath();
        FileDto fileDto = ReadFileUtils.readPpt(path);
        System.out.println(fileDto);
    }
}
